package com.jdd.common.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jdd.common.constant.JddConstant;
import com.jdd.common.exception.JddException;
import com.jdd.modules.area.entity.ChargeRules;
import com.jdd.modules.area.entity.ChargeRulesModule;
import com.jdd.modules.area.service.IChargeRulesModuleService;
import com.jdd.modules.area.service.IChargeRulesModuleStepService;
import com.jdd.modules.area.service.IChargeRulesService;
import com.jdd.modules.configcartype.entity.ConfigCarType;
import com.jdd.modules.configcartype.mapper.ConfigCarTypeMapper;
import com.jdd.modules.parkcar.entity.ParkCarMonthly;
import com.jdd.modules.parkcar.entity.ParkInnerInfo;
import com.jdd.modules.parkcar.mapper.ParkInnerListMapper;
import com.jdd.modules.parkcar.service.IParkCarMonthlyService;
import com.jdd.modules.parking.entity.ParkCarEnterLog;
import com.jdd.modules.parkmonthly.entity.MonthlyCardInfo;
import com.jdd.modules.parkmonthly.entity.MonthlyPackageInfo;
import com.jdd.modules.parkmonthly.service.IMonthlyCardInfoService;
import com.jdd.modules.parkmonthly.service.IMonthlyPackageInfoService;
import com.jdd.modules.sdkidentifyresultpushlog.dto.ParkGetCarFeeLogV3DTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 算费工具
 *
 * @author LiuYaoWen
 * @date 2021-08-16
 */
@Component
@Slf4j
public class ChargeFeeUtils {
    @Resource
    private IParkCarMonthlyService parkCarMonthlyService;
    @Resource
    private IChargeRulesService chargeRulesService;
    @Resource
    private IChargeRulesModuleService chargeRulesModuleService;
    @Resource
    private IChargeRulesModuleStepService chargeRulesModuleStepService;
    @Resource
    private IMonthlyCardInfoService monthlyCardInfoService;
    @Resource
    private IMonthlyPackageInfoService monthlyPackageInfoService;
    @Resource
    private PackageFeeLogUtils packageFeeLogUtils;
    @Resource
    private ParkInnerListMapper parkInnerListMapper;
    @Resource
    private ConfigCarTypeMapper configCarTypeMapper;

    /**
     * 该方法正式开始计费：根据车辆所有的入场纪录进行遍历算费
     *
     * @param parkCarEnterLogList 车辆入场纪录
     * @return 总计费
     * @author: LiuYaoWen
     * @date 2021-07-14
     */
    public Long chargeTotalFee(List<ParkCarEnterLog> parkCarEnterLogList, String carTypeId) {
        long totalFee = 0;
        ParkCarEnterLog firstEnterLog = parkCarEnterLogList.get(0);
        log.info("计费服务类,计费开始,车牌号：{}", firstEnterLog.getPlateNo());
        List<ParkCarMonthly> parkCarMonthlyList = null;
        // 如果车辆是有牌车
        if (StringUtils.isNotBlank(firstEnterLog.getPlateNo())) {
            // 查询车辆在该车场的所有的月租车信息
            parkCarMonthlyList = parkCarMonthlyService.getMonthlyListInPark(firstEnterLog.getParkCode(), firstEnterLog.getPlateNo(), JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        }
        // 对入场纪录进行遍历计费,以本次入场纪录做入场纪录,下次入场纪录做出场识别信息进行计费
        for (int index = 0; index < parkCarEnterLogList.size() - 1; index++) {
            // 得到本次停车的入场纪录
            ParkCarEnterLog enterLog = parkCarEnterLogList.get(index);
            // 得到本次停车的出场纪录
            ParkCarEnterLog nextEnterLog = parkCarEnterLogList.get(index + 1);
            // 得到本次停车的区域id
            String areaId = enterLog.getAreaId();
            // 得到本次停车的入场时间
            Date carEnterTime = DateUtil.parse(enterLog.getCarEnterTime(), "yyyy-MM-dd HH:mm:ss");
            // 得到本次停车的出场时间
            Date carOutTime = DateUtil.parse(nextEnterLog.getCarEnterTime(), "yyyy-MM-dd HH:mm:ss");

            // 如果车辆在整个车场内都不是月租车
            if (CollectionUtil.isEmpty(parkCarMonthlyList)) {
                // 得到该区域的计费总规则
                ChargeRules chargeRules = chargeRulesService.getChargeRules(carTypeId, areaId);
                if (null == chargeRules) {
                    log.error("计费规则不存在,区域Id:{},车辆类型id:{}", areaId, carTypeId);
                    throw new JddException(JddConstant.ChargeConstant.CHARGE_FEE_FIND_NULL, "计费规则不存在");
                }
                // 算费累加
                totalFee = totalFee + chargeEnterLogFee(carEnterTime, carOutTime, chargeRules);
            } else {
                // 根据入场纪录中的车位号,得到月租车信息
                ParkCarMonthly parkCarMonthly = parkCarMonthlyList.stream().filter(car -> car.getParkNumber().equals(enterLog.getParkNumber())).findFirst().orElse(null);
                log.info("判断车辆是否是月租车，月租车信息：{}", parkCarMonthly);
                if (null != parkCarMonthly) {
                    log.info("车辆是月租车，区域：{}", enterLog.getAreaId());
                    // 如果该次停车时,月租车已过期,即过期时间早于车辆出场时间
                    if (parkCarMonthly.getEndTime().before(carOutTime)) {
                        log.info("月租车位场内过期，将按照过期时间开始计费，过期时间：{}", parkCarMonthly.getEndTime());
                        //内部车判断
                        ParkInnerInfo isInner = parkInnerListMapper.getIsInner(firstEnterLog.getParkCode(), firstEnterLog.getPlateNo());
                        if (isInner != null) {
                            carTypeId = getCarTypeId(firstEnterLog.getParkCode(), String.valueOf(JddConstant.TemporaryCarType.TEMPORARY_CAR_INNER));
                        }

                        ChargeRules chargeRules = chargeRulesService.getChargeRules(carTypeId, areaId);
                        totalFee = totalFee + chargeEnterLogFee(parkCarMonthly.getEndTime(), carOutTime, chargeRules);
                    }
                    log.info("月租车位未过期，不产生任何费用");
                } else {
                    // 如果车辆在该区域内不是月租车
                    // 得到该区域的计费总规则
                    ChargeRules chargeRules = chargeRulesService.getChargeRules(carTypeId, areaId);
                    if (null == chargeRules) {
                        log.error("计费规则不存在,区域Id:{},车辆类型id:{}", areaId, carTypeId);
                        throw new JddException(JddConstant.ChargeConstant.CHARGE_FEE_FIND_NULL, "计费规则不存在");
                    }
                    // 算费累加
                    totalFee = totalFee + chargeEnterLogFee(carEnterTime, carOutTime, chargeRules);
                }
            }
        }
        return totalFee;
    }

    public Long chargeTotalFeeMonthlyV3(List<ParkCarEnterLog> parkCarEnterLogList, String chargeFeeCarTypeId, ParkGetCarFeeLogV3DTO parkGetCarFeeLogV3DTO) {
        long totalFee = 0;
        ParkCarEnterLog firstEnterLog = parkCarEnterLogList.get(0);
        log.info("计费服务类,计费开始,车牌号：{}", firstEnterLog.getPlateNo());

        List<ParkCarMonthly> parkCarMonthlyList = new ArrayList<>();
        MonthlyPackageInfo monthlyPackageInfo = null;
        MonthlyCardInfo monthlyCardInfo = null;
        List<MonthlyCardInfo> monthlyCardInfos = new ArrayList<>();
        List<MonthlyPackageInfo> monthlyPackageInfos = new ArrayList<>();
        // 如果车辆是有牌车
        if (StringUtils.isNotBlank(firstEnterLog.getPlateNo())) {
            // 查询车辆在该车场的所有的月租车信息
            parkCarMonthlyList = parkCarMonthlyService.getMonthlyListInParkV3(firstEnterLog.getParkCode(), firstEnterLog.getPlateNo(), JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
            if (CollectionUtil.isNotEmpty(parkCarMonthlyList)) {
                List<String> monthlyCardInfoIds = parkCarMonthlyList.stream().map(ParkCarMonthly::getMonthlyCardId).distinct().collect(Collectors.toList());
                if (CollectionUtil.isEmpty(monthlyCardInfoIds)) {
                    log.error("错误的月卡数据，此次计费不计算月卡信息");
                    parkCarMonthlyList = null;
                } else {
                    monthlyCardInfos = (List<MonthlyCardInfo>) monthlyCardInfoService.listByIds(monthlyCardInfoIds);
                    if (ObjectUtil.isEmpty(monthlyCardInfos)) {
                        log.error("错误的月租套餐数据，此次计费不计算月卡信息");
                        parkCarMonthlyList = null;
                    } else {
                        List<String> monthlyPackageInfoIds = monthlyCardInfos.stream().map(MonthlyCardInfo::getPackageId).collect(Collectors.toList());
                        monthlyPackageInfos = (List<MonthlyPackageInfo>) monthlyPackageInfoService.listByIds(monthlyPackageInfoIds);
                        if (CollectionUtil.isEmpty(monthlyPackageInfos)) {
                            log.error("错误的月租套餐数据，此次计费不计算月卡信息");
                            parkCarMonthlyList = null;
                        }
                    }
                }
            }
        }
        // 对入场纪录进行遍历计费,以本次入场纪录做入场纪录,下次入场纪录做出场识别信息进行计费
        for (int index = 0; index < parkCarEnterLogList.size() - 1; index++) {
            // 得到本次停车的入场纪录
            ParkCarEnterLog enterLog = parkCarEnterLogList.get(index);
            // 得到本次停车的出场纪录
            ParkCarEnterLog nextEnterLog = parkCarEnterLogList.get(index + 1);
            // 得到本次停车的区域id
            String areaId = enterLog.getAreaId();
            // 获取当前入场的判定车辆类型id
            String carTypeId = StringUtils.isBlank(chargeFeeCarTypeId) ? enterLog.getCarTypeId() : chargeFeeCarTypeId;
            // 得到本次停车的入场时间
            Date carEnterTime = DateUtil.parse(enterLog.getCarEnterTime(), "yyyy-MM-dd HH:mm:ss");
            // 得到本次停车的出场时间
            Date carOutTime = DateUtil.parse(nextEnterLog.getCarEnterTime(), "yyyy-MM-dd HH:mm:ss");
            // 如果车辆在整个车场内都不是月租车
            if (StringUtils.isNotBlank(enterLog.getParkNumber()) && CollectionUtil.isNotEmpty(parkCarMonthlyList)) {
                List<MonthlyCardInfo> inAreaMonthlyInfos = monthlyCardInfos.stream().filter(info -> info.getId().equals(enterLog.getMonthlyCardId())).collect(Collectors.toList());
                if (CollectionUtil.isEmpty(inAreaMonthlyInfos) || inAreaMonthlyInfos.size() > 1) {
                    log.error("异常的月租车数据，{}", inAreaMonthlyInfos);
                    throw new JddException(ResultCode.INTERNAL_SERVER_ERROR, "异常的月租车数据");
                }
                monthlyCardInfo = inAreaMonthlyInfos.get(0);
                if (ObjectUtil.isNotNull(parkGetCarFeeLogV3DTO)) {
                    parkGetCarFeeLogV3DTO.setMonthlyCardInfo(monthlyCardInfo);
                }
                List<MonthlyPackageInfo> inAreaMonthlyPackageInfo = monthlyPackageInfos.stream().filter(info -> info.getId().equals(inAreaMonthlyInfos.get(0).getPackageId())).collect(Collectors.toList());
                if (CollectionUtil.isEmpty(inAreaMonthlyPackageInfo) || inAreaMonthlyPackageInfo.size() > 1) {
                    log.error("异常的月租套餐数据，{}", inAreaMonthlyPackageInfo);
                    throw new JddException(ResultCode.INTERNAL_SERVER_ERROR, "异常的月租车数据");
                }
                monthlyPackageInfo = inAreaMonthlyPackageInfo.get(0);
                // 如果该次停车时,月租车已过期,即过期时间早于车辆出场时间
                if (monthlyCardInfo.getEndTime().before(carOutTime)) {
                    log.info("月租车位场内过期，将按照过期时间开始计费，过期时间：{}", monthlyCardInfo.getEndTime());
                    carEnterTime = monthlyCardInfo.getEndTime();
                    carTypeId = monthlyPackageInfo.getOverdueTypeId();
                    if (JddConstant.MonthlyPackageConfigInfo.OVERTIME_USE_ENTER_CAR_TYPE_OUT.equals(carTypeId)) {
                        carTypeId = packageFeeLogUtils.getCarTypeId(enterLog.getParkCode(), enterLog.getCarTypeNo());
                    }
                } else {
                    log.info("月租车位未过期，不产生任何费用");
                    continue;
                }
            }
            // 得到该区域的计费总规则
            ChargeRules chargeRules = chargeRulesService.getChargeRules(carTypeId, areaId);
            // 如果车辆入场时间早于车辆出场时间，则进行算费
            if (carEnterTime.before(carOutTime)) {
                // 算费累加
                totalFee = totalFee + chargeEnterLogFee(carEnterTime, carOutTime, chargeRules);
            }
        }
        return totalFee;
    }

    /**
     * 单次入场纪录计费
     *
     * @param carEnterTime 车辆入场时间
     * @param carOutTime   车辆出场时间
     * @param chargeRules  统一的计费规则
     * @return 算费金额
     * @author: LiuYaoWen
     * @date 2021-07-14
     */
    public long chargeEnterLogFee(Date carEnterTime, Date carOutTime, ChargeRules chargeRules) {
        if (null == chargeRules) {
            throw new JddException(ResultCode.PARAMETER_ERROR, "计费规则不存在");
        }

        log.info("计费服务类,单次入场纪录计费,传入参数：计费区域Id：{},计费开始时间：{},计费结束时间：{}", chargeRules.getAreaId(), carEnterTime, carOutTime);

        long chargeFee = 0;
        // 首先判断是否处于免费时间
        if (isInFreeTime(carEnterTime, carOutTime, chargeRules)) {
            log.info("车辆处于免费时间内，计费结果为0");
            return chargeFee;
        }
        // 得到停车时间
        long betweenMinutes = DateUtil.between(carEnterTime, carOutTime, DateUnit.MINUTE);
        // 得到该车场区域的所有的时间模块计费规则
        List<ChargeRulesModule> chargeRulesModuleList = chargeRulesModuleService.getChargeRulesModule(chargeRules.getId());

        // 判段该区域是否按24小时计算一天。1：按24小时,2：按自然日
        if (JddConstant.ChargeConstant.TWENTY_FOUR_HOUR_ENABLE_TRUE == chargeRules.getTwentyfourHourEnable()) {
            // 按24小时计算一天
            // 得到停车天数
            long days = betweenMinutes / JddConstant.ChargeConstant.DAY_NIGHT_MINUTES;
            // 得到不足一天的停车时间
            long dayRemainderMinutes = betweenMinutes % JddConstant.ChargeConstant.DAY_NIGHT_MINUTES;
            log.info("计费服务类,单次入场纪录计费,按24小时计算一天,停车整天数：{}天,不足整天的停车时间：{}分钟", days, dayRemainderMinutes);
            // 如果停车天数不为0
            if (days != 0) {
                // 首先计算第一天的计费金额,且计算首计时
                chargeFee = oneDayFee(carEnterTime, chargeRules, chargeRulesModuleList, true);
                if (days != 1) {
                    // 如果停车天数不止一天,再次计算剩余天数的整天费用,且不计算首计时费用
                    chargeFee = chargeFee + (days - 1) * oneDayFee(carEnterTime, chargeRules, chargeRulesModuleList, false);
                }
                // 如果不存在不足一天的停车时间
                if (dayRemainderMinutes == 0) {
                    long dayRemainderSecond = DateUtil.between(carEnterTime, carOutTime, DateUnit.SECOND) - (betweenMinutes * 60);
                    if (dayRemainderSecond == 0) {
                        log.info("计费服务类,单次入场纪录计费,计算结果：{}", chargeFee);
                        return chargeFee;
                    }
                }
                Calendar chargeStartTimeCalendar = Calendar.getInstance();
                chargeStartTimeCalendar.setTime(carEnterTime);
                chargeStartTimeCalendar.add(Calendar.DAY_OF_MONTH, (int) days);
                chargeFee = chargeFee + lessThanOneDayFee(chargeStartTimeCalendar.getTime(), carOutTime, chargeRules, chargeRulesModuleList, false);
            } else {
                // 如果停车天数为0
                chargeFee = chargeFee + lessThanOneDayFee(carEnterTime, carOutTime, chargeRules, chargeRulesModuleList, true);
            }
        }
        // 如果该区域是按自然日计算一天
        if (JddConstant.ChargeConstant.TWENTY_FOUR_HOUR_ENABLE_FALSE == chargeRules.getTwentyfourHourEnable()) {
            // 得到入场当天的结束时间
            Date enterDayEndTime = DateUtils.getOneDayEndDate(carEnterTime, chargeRules.getTwentyfourHourEnable());
            // 得到出场当天的开始时间
            Date outDayStartTime = DateUtils.getOneDayStartDate(carOutTime);

            // 判断出场当天的开始时间是否早于入场当天的开始时间,true：不跨天,false：跨天
            // 两者相等时，false
            if (outDayStartTime.before(enterDayEndTime)) {
                log.info("计费服务类,单次入场纪录计费,按自然日计算一天,不存在跨天情况");
                // 不跨天
                chargeFee = chargeFee + lessThanOneDayFee(carEnterTime, carOutTime, chargeRules, chargeRulesModuleList, true);
            } else {
                log.info("计费服务类,单次入场纪录计费,按自然日计算一天,存在跨天情况");
                //1号13点00分入场，3号11点00分出场
                //1号13点到1号24点的
                chargeFee = chargeFee + lessThanOneDayFee(carEnterTime, enterDayEndTime, chargeRules, chargeRulesModuleList, true);
                long days = DateUtil.between(enterDayEndTime, outDayStartTime, DateUnit.DAY);
                if (days != 0) {
                    //2号整天的
                    chargeFee = chargeFee + days * oneDayFee(enterDayEndTime, chargeRules, chargeRulesModuleList, false);
                    log.info("计算跨天费用,跨天数：{},跨天金额：{}", days, chargeFee);
                }
                //3号0点到11点的
                chargeFee = chargeFee + lessThanOneDayFee(outDayStartTime, carOutTime, chargeRules, chargeRulesModuleList, false);
            }
        }
        if(JddConstant.ChargeConstant.TWENTY_FOUR_HOUR_NO_DIVIDE == chargeRules.getTwentyfourHourEnable())
        {
            chargeFee = chargeFee + lessThanOneDayFee(carEnterTime,carOutTime,chargeRules,chargeRulesModuleList,true);
        }
        log.info("计费服务类,单次入场纪录计费,计算结果：{}", chargeFee);
        return chargeFee;
    }

    /**
     * 计算整天费用
     *
     * @param chargeStartTime       计费开始时间
     * @param chargeRules           统一的计费规则
     * @param chargeRulesModuleList 计费规则模块
     * @param firstCharge           是否收取首计费费用
     * @return 计费金额
     * @date 2021-7-15
     * @author: LiuYaoWen
     */
    private  long oneDayFee(Date chargeStartTime, ChargeRules chargeRules, List<ChargeRulesModule> chargeRulesModuleList, boolean firstCharge) {
        // 根据计费开始时间,以及一天的判断类型,得到计费结束时间
        Date nowDayEndDate = DateUtils.getOneDayEndDate(chargeStartTime, chargeRules.getTwentyfourHourEnable());
        log.info(">>>>>>>>>>计算整天费用,计费开始时间：{},计费结束时间：{},是否收取首计时费用：{}", DateUtil.format(chargeStartTime, "YYYY-MM-dd HH:mm:ss"), DateUtil.format(nowDayEndDate, "YYYY-MM-dd HH:mm:ss"), firstCharge);
        long fee = lessThanOneDayFee(chargeStartTime, nowDayEndDate, chargeRules, chargeRulesModuleList, firstCharge);
        log.info(">>>>>>>>>>计算整天费用,计费结果：{}", fee);
        return fee;
    }

    /**
     * 计算不满一天的费用
     *
     * @param chargeStartTime       计费开始时间
     * @param chargeEndTime         计费结束时间
     * @param chargeRulesModuleList 计费规则模块
     * @param firstCharge           是否收取首计费费用
     */
    private  long lessThanOneDayFee(Date chargeStartTime, Date chargeEndTime, ChargeRules chargeRules, List<ChargeRulesModule> chargeRulesModuleList, boolean firstCharge) {
        long fee = 0;
        log.info(">>>>>>>>>>计算不足整天的费用");
        if (CollectionUtil.isEmpty(chargeRulesModuleList)) {
            throw new JddException(ResultCode.PARAMETER_ERROR, "计费规则模块不存在");
        }
        // 如果区域计费规则模块只有一个,特殊情况单独处理
        if (chargeRulesModuleList.size() == 1) {
            fee = fee + chargeRulesModulesFee(chargeStartTime, chargeEndTime, chargeRulesModuleList.get(0), firstCharge);
        } else {
            // 计费开始时间日期换算
            Calendar chargeStartTimeCalendar = Calendar.getInstance();
            chargeStartTimeCalendar.setTime(chargeStartTime);
            ChargeRulesModule chargeRulesModule = new ChargeRulesModule();
            Date chargeRulesModuleEndTime;
            Date chargeRulesModuleStartTime;
            // 开始计费
            while (chargeStartTime.before(chargeEndTime)) {
                // 得到计费规则列表中的最后一条
                ChargeRulesModule firstChargeRulesModule = chargeRulesModuleList.get(chargeRulesModuleList.size() - 1);
                // 计费开始时间的前一天
                Date yesterday = DateUtil.offsetDay(chargeStartTime, -1);
                // 得到计费开始时间前一天的最后一个计费规则模块的计费开始时间
                chargeRulesModuleStartTime = getChargeRulesModulesStartTime(yesterday, firstChargeRulesModule);
                // 得到计费开始时间前一天的最后一个计费规则模块的计费结束时间
                chargeRulesModuleEndTime = getChargeRulesModulesEndTime(yesterday, firstChargeRulesModule);
                // 判断计费开始时间是否处于该计费模块内
                if (isInChargeRulesModule(chargeStartTime, chargeRulesModuleStartTime, chargeRulesModuleEndTime)) {
                    chargeRulesModule = firstChargeRulesModule;
                } else {
                    // 开始遍历计费开始时间当天的计费规则模块
                    for (ChargeRulesModule module : chargeRulesModuleList) {
                        // 得到计费规则模块的计费开始时间
                        chargeRulesModuleStartTime = getChargeRulesModulesStartTime(chargeStartTime, module);
                        // 得到计费规则模块的计费结束时间
                        chargeRulesModuleEndTime = getChargeRulesModulesEndTime(chargeStartTime, module);
                        // 判断计费开始时间是否处于该计费模块内
                        if (isInChargeRulesModule(chargeStartTime, chargeRulesModuleStartTime, chargeRulesModuleEndTime)) {
                            chargeRulesModule = module;
                            break;
                        }
                    }
                }
                log.info("得到计费规则模块，计费规则模块开始时间：{}，结束时间：{}", DateUtil.format(chargeRulesModuleStartTime, "yyyy-MM-dd HH:mm:ss"), DateUtil.format(chargeRulesModuleEndTime, "yyyy-MM-dd HH:mm:ss"));

                if(JddConstant.ChargeConstant.CHARGE_MODE_TIME.equals(chargeRulesModule.getChargeType()))
                {
                    if(JddConstant.ChargeConstant.TIME_REMAINDER_MODE_NEXT_UP.equals(chargeRulesModule.getNotEnough()))
                    {
                        Date nextTime ;
                        nextTime = getRangeNextTime(chargeStartTime,chargeRulesModuleEndTime,chargeRulesModule,firstCharge);
                        // 判断计费结束时间是否晚于计费规则模块的计费结束时间
                        if (chargeEndTime.after(nextTime)) {
                            // 停车费用累加计费规则模块费用,开始：计费开始时间,结束：计费规则模块的计费结束时间
                            fee = fee + chargeRulesModulesFee(chargeStartTime, nextTime, chargeRulesModule, firstCharge);
                        } else {
                            // 停车费用累加计费规则模块费用,开始：计费开始时间,结束：计费结束时间
                            fee = fee + chargeRulesModulesFee(chargeStartTime, chargeEndTime, chargeRulesModule, firstCharge);
                        }
                        // 不再计算首计时费用
                        firstCharge = false;
                        // 计费开始时间向前平移一个 从计费开始时间到当前计费规则模块的计费结束时间的长度
                        chargeStartTimeCalendar.add(Calendar.SECOND, (int) DateUtil.between(chargeStartTime, nextTime, DateUnit.SECOND));
                        chargeStartTime = chargeStartTimeCalendar.getTime();
                        continue;
                    }
                }
                // 判断计费结束时间是否晚于计费规则模块的计费结束时间
                if (chargeEndTime.after(chargeRulesModuleEndTime)) {
                    // 停车费用累加计费规则模块费用,开始：计费开始时间,结束：计费规则模块的计费结束时间
                    fee = fee + chargeRulesModulesFee(chargeStartTime, chargeRulesModuleEndTime, chargeRulesModule, firstCharge);
                } else {
                    // 停车费用累加计费规则模块费用,开始：计费开始时间,结束：计费结束时间
                    fee = fee + chargeRulesModulesFee(chargeStartTime, chargeEndTime, chargeRulesModule, firstCharge);
                }
                // 不再计算首计时费用
                firstCharge = false;
                // 计费开始时间向前平移一个 从计费开始时间到当前计费规则模块的计费结束时间的长度
                chargeStartTimeCalendar.add(Calendar.SECOND, (int) DateUtil.between(chargeStartTime, chargeRulesModuleEndTime, DateUnit.SECOND));
                chargeStartTime = chargeStartTimeCalendar.getTime();
            }
        }
        // 如果每日最高限额启用,且停车费用大于每日最高限额
        if (fee > chargeRules.getDayLimit() && JddConstant.ChargeConstant.LIMIT_MONEY_ENABLE_TRUE == chargeRules.getDayLimitEnable()) {
            // 停车费用为每日最高限额
            fee = chargeRules.getDayLimit();
        }
        log.info(">>>>>>>>>>计算不足整天的费用,计算结果：{}", fee);
        return fee;
    }

    /**
     * 计算计费模块费用
     *
     * @param chargeStartTime   计费开始时间
     * @param chargeEndTime     计费结束时间
     * @param chargeRulesModule 计费模块
     * @return 计费模块费用
     * @date 2021-07-15
     * @author: LiuYaoWen
     */
    private  long chargeRulesModulesFee(Date chargeStartTime, Date chargeEndTime, ChargeRulesModule chargeRulesModule, boolean firstCharge) {
        log.info("计费服务类,计算计费模块费用,传入参数：模块计费开始时间：{},模块计费结束时间:{}", DateUtil.format(chargeStartTime, "YYYY-MM-dd HH:mm:ss"), DateUtil.format(chargeEndTime, "YYYY-MM-dd HH:mm:ss"));
        long fee = 0;
        // 如果当前计费规则模块是按次收费
        if (JddConstant.ChargeConstant.CHARGE_MODE_ONE_TIMES.equals(chargeRulesModule.getChargeType())) {
            fee = chargeRulesModule.getOnceMoney();
            log.info("计费服务类,计算计费模块费用,计费结果：{}", fee);
            return fee;
        }
        // 如果当前计费规则模块是按时间增量计费
        if (JddConstant.ChargeConstant.CHARGE_MODE_TIME.equals(chargeRulesModule.getChargeType())) {
            // 得到当前计费模块计费总时间
            long betweenMinute = DateUtil.between(chargeStartTime, chargeEndTime, DateUnit.MINUTE);
            // 得到不足分钟的秒数
            long betweenSecond = DateUtil.between(chargeStartTime, chargeEndTime, DateUnit.SECOND) - betweenMinute * 60;
            // 如果需要收取首计时费用
            if (firstCharge) {
                // 停车费用累加一个首计时费用
                fee = fee + chargeRulesModule.getFirstMoney();
                if (isInFirstTime(betweenMinute, betweenSecond, chargeRulesModule.getFirstUnit())) {
                    log.info("车辆处于停车首计时内，计算金额：{}", fee);
                    return fee;
                }
                // 计费开始时间平移一个首计时单位
                Calendar chargeStartTimeCalendar = Calendar.getInstance();
                chargeStartTimeCalendar.setTime(chargeStartTime);
                chargeStartTimeCalendar.add(Calendar.MINUTE, chargeRulesModule.getFirstUnit());
                chargeStartTime = chargeStartTimeCalendar.getTime();
                // 得到平移后的计费模块计费总时间
                betweenMinute = DateUtil.between(chargeStartTime, chargeEndTime, DateUnit.MINUTE);
                // 得到平移后的不足分钟的秒数
                betweenSecond = DateUtil.between(chargeStartTime, chargeEndTime, DateUnit.SECOND) - betweenMinute * 60;
            }
            // 得到停车单位数
            long timeUnits = betweenMinute / chargeRulesModule.getTimeUnit();
            // 得到不足一个停车单位的分钟数
            long timeMinutes = betweenMinute % chargeRulesModule.getTimeUnit();
            if (betweenSecond != 0) {
                timeMinutes++;
            }
            // 如果不足一个停车单位按一个单位计算
            if (JddConstant.ChargeConstant.TIME_REMAINDER_MODE_Y.equals(chargeRulesModule.getNotEnough())||JddConstant.ChargeConstant.TIME_REMAINDER_MODE_NEXT_UP.equals(chargeRulesModule.getNotEnough())) {
                if (timeMinutes != 0) {
                    timeUnits++;
                }
            }
            //计算总费用
            fee = fee + timeUnits * chargeRulesModule.getMoneyUnit();
            // 如果区间最高限额启用
            if (JddConstant.ChargeConstant.MONEY_LIMIT_ENABLE.equals(chargeRulesModule.getRangeLimitEnable())) {
                if (fee > chargeRulesModule.getRangeLimit()) {
                    fee = chargeRulesModule.getRangeLimit();
                }
            }
            log.info("计费服务类,计算计费模块费用,计费结果：{}", fee);
            return fee;
        }
        // 如果当前计费规则模块是按照阶梯计费
        if(JddConstant.ChargeConstant.CHARGE_MODE_STEP.equals(chargeRulesModule.getChargeType()))
        {
            long parkTimeSecond = DateUtil.between(chargeStartTime,chargeEndTime,DateUnit.SECOND);
            Integer parkTimeMinute = new BigDecimal(parkTimeSecond).divide(new BigDecimal("60"), RoundingMode.UP).intValue();
            Integer stepFee = chargeRulesModuleStepService.getNowTimeStep(chargeRulesModule.getId(),parkTimeMinute);
            if(null == stepFee)
            {
                stepFee = chargeRulesModuleStepService.getMaxTimeStep(chargeRulesModule.getId());
            }
            if(stepFee>chargeRulesModule.getRangeLimit()&&JddConstant.ChargeConstant.MONEY_LIMIT_ENABLE.equals(chargeRulesModule.getRangeLimitEnable()))
            {
                stepFee = chargeRulesModule.getRangeLimit();
            }
            log.info("计费服务类,计算计费模块费用,计费结果：{}", stepFee);
            return stepFee;
        }

        // 如果当前计费规则模块,不是按次收费,也不是按时间增量计费
        throw new JddException(ResultCode.PARAMETER_ERROR, "当前计费规则模块错误,错误计费规则模块id:" + chargeRulesModule.getId());
    }

    /**
     * 判定车辆是否在免费时间段内
     *
     * @param carEnterTime 入场时间
     * @param carOutTime   出场时间
     * @param chargeRules  计费规则
     * @return true 在免费时间段内,false 不在免费时间段内
     * @author: LiuYaoWen
     * @date 2021-07-14
     */
    private static boolean isInFreeTime(Date carEnterTime, Date carOutTime, ChargeRules chargeRules) {
        // 得到停车时间（分）
        long betweenMinutes = DateUtil.between(carEnterTime, carOutTime, DateUnit.MINUTE);
        // 如果该计费规则的免费时间是未启用,则立即返回false
        if (JddConstant.ChargeConstant.FREE_TIME_STATUS_CLOSE.equals(chargeRules.getFreeTimeEnable())) {
            // 判定停车时间是否小于1分钟
            return betweenMinutes == 0;
        }
        // 得到停车时间按分计算的余数（秒）
        long betweenSecond = DateUtil.between(carEnterTime, carOutTime, DateUnit.SECOND) - betweenMinutes * 60;
        // 如果停车时间小于免费时间或停车时间等于免费时间,返回true,否则返回false
        return betweenMinutes < chargeRules.getFreeTime() || (betweenMinutes == chargeRules.getFreeTime() && betweenSecond == 0);
    }

    private static boolean isInFirstTime(Long betweenMinute, Long betweenSecond, Integer firstTime) {
        // 如果当前计费模块计费总时间小于或等于首计时费用,
        return betweenMinute < firstTime || (betweenMinute == firstTime.longValue() && betweenSecond == 0);
    }


    /**
     * 根据入场时间,以及计费规则模块,得到当前计费规则模块的计费时间区间开始时间
     *
     * @param carEnterTime      入场时间
     * @param chargeRulesModule 计费规则模块
     * @return 计费时间区间开始时间
     * @author: LiuYaoWen
     * @date 2021-07-19
     */
    private static Date getChargeRulesModulesStartTime(Date carEnterTime, ChargeRulesModule chargeRulesModule) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(carEnterTime);
        calendar.set(Calendar.HOUR_OF_DAY, chargeRulesModule.getStartTimeHour());
        calendar.set(Calendar.MINUTE, chargeRulesModule.getStartTimeMinute());
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    /**
     * 根据入场时间,以及计费规则模块,得出当前计费规则模块的计费时间区域结束时间
     *
     * @param carEnterTime      入场时间
     * @param chargeRulesModule 计费规则模块
     * @return 当前计费规则模块的计费时间区域结束时间
     * @date 2021-07-15
     * @author: LiuYaoWen
     */
    private static Date getChargeRulesModulesEndTime(Date carEnterTime, ChargeRulesModule chargeRulesModule) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(carEnterTime);
        // 如果计费时间区域开始时间（时）大于计费时间区域结束时间（时）
        if (chargeRulesModule.getStartTimeHour() > chargeRulesModule.getEndTimeHour()) {
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }
        // 如果计费时间区域开始时间（时）等于计费时间区域时间（时）
        if (chargeRulesModule.getStartTimeHour().equals(chargeRulesModule.getEndTimeHour())) {
            // 如果计费时间区域开始时间（分）大于等于计费时间区域结束时间
            if (chargeRulesModule.getStartTimeMinute() >= chargeRulesModule.getEndTimeMinute()) {
                calendar.add(Calendar.DAY_OF_MONTH, 1);
            }
        }
        calendar.set(Calendar.HOUR_OF_DAY, chargeRulesModule.getEndTimeHour());
        calendar.set(Calendar.MINUTE, chargeRulesModule.getEndTimeMinute());
        calendar.set(Calendar.SECOND, 0);

        return calendar.getTime();
    }

    /**
     * 判断计费时间是否处于计费规则模块
     *
     * @param chargeStartTime 计费开始时间
     * @param moduleStartTime 计费规则模块计费开始时间
     * @param moduleEndTime   计费规则模块计费结束时间
     * @return true 计费时间在计费规则模块内
     * @date 2021-07-28
     * @author: LiuYaoWen
     */
    private static boolean isInChargeRulesModule(Date chargeStartTime, Date moduleStartTime, Date moduleEndTime) {
        // 如果计费开始时间,晚于计费模块开始时间,早于计费模块结束时间,或计费开始时间等于计费模块开始时间
        return (chargeStartTime.after(moduleStartTime) && chargeStartTime.before(moduleEndTime)) || chargeStartTime.equals(moduleStartTime);
    }

    /**
     * 得到车辆类型id
     *
     * @param parkCode  车场编号
     * @param carNature 车辆性质
     * @return 车辆类型id
     * @date 2021-07-28
     * @author: liuyaowen
     */
    private String getCarTypeId(String parkCode, String carNature) {
        LambdaQueryWrapper<ConfigCarType> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ConfigCarType::getParkCode, parkCode);
        List<ConfigCarType> list = configCarTypeMapper.selectList(queryWrapper);
        for (ConfigCarType configCarType : list) {
            List<String> carTypes = Arrays.asList(configCarType.getCarTypeNo().split(","));
            if (carTypes.contains(carNature)) {
                return configCarType.getId();
            }
        }
        return null;
    }
    private static Date getRangeNextTime(Date carEnterTime,Date chargeEndTime, ChargeRulesModule chargeRulesModule,Boolean isFirst){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(carEnterTime);
        if(isFirst)
        {
            calendar.add(Calendar.MINUTE,chargeRulesModule.getFirstUnit());
        }
        Date afterAddTime = calendar.getTime();
        if(afterAddTime.after(chargeEndTime))
        {
            return afterAddTime;
        }
        long second = DateUtil.between(afterAddTime,chargeEndTime,DateUnit.SECOND);
        long unit = second/(chargeRulesModule.getTimeUnit()*60);
        long remainder = second%(chargeRulesModule.getTimeUnit()*60);
        if(remainder!=0)
        {
            unit++;
        }
        calendar.add(Calendar.MINUTE, (int) (chargeRulesModule.getTimeUnit()*unit));
        return calendar.getTime();
    }
    private static Date getFirstTimeNextTime(Date carEnterTime, ChargeRulesModule chargeRulesModule){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(carEnterTime);
        calendar.add(Calendar.MINUTE,chargeRulesModule.getFirstUnit());
        return calendar.getTime();
    }
}
